import { UploadOutlined } from '@ant-design/icons'
import { Button, Upload } from 'antd'
import dayjs from 'dayjs'
import { isEmpty, uniqueId } from 'lodash-es'
import { forwardRef, useEffect, useState } from 'react'

import Preview from '@/components/Preview'
import { message } from '@/utils/antdHelper'
import config from '@/utils/config'
import Crypto from '@/utils/crypto-js'
import Base64 from '@/utils/crypto-js/base64'
import EnumManager, { FILE_TYPE_ENUM, FILE_UPLOAD_TYPE_ENUM } from '@/utils/enum'
import { randomString } from '@/utils/stringUtil'

// import AUDIO_IMG from './image/audio.png'
import PDF_IMG from './image/pdf.png'
// import VIDEO_IMG from './image/video.png'
import styles from './index.module.less'

/** 文件状态 */
const FILE_STATUS_ENUM = new EnumManager({
  DONE: { value: 'done', label: '成功' },
  UPLOADING: { value: 'uploading', label: '上传中' },
  ERROR: { value: 'error', label: '错误' },
  REMOVED: { value: 'removed', label: '删除' },
})

const { REQUEST_SUCCESS_CODE } = config

const imgUploadButton = <UploadOutlined fontSize={24} />

const uploadButton = <Button size='mini'>上传</Button>

const execlAccept = FILE_UPLOAD_TYPE_ENUM.EXECL

const docAccept = FILE_UPLOAD_TYPE_ENUM.WORD

const imgAccept = FILE_UPLOAD_TYPE_ENUM.IMAGE

const videoAccept = FILE_UPLOAD_TYPE_ENUM.VIDEO

const pdfAccept = FILE_UPLOAD_TYPE_ENUM.PDF

const acceptType = {
  execl: execlAccept,
  doc: docAccept,
  img: imgAccept,
  imgAndPdf: `${imgAccept},${pdfAccept}`,
  imgAndVideo: `${imgAccept},${videoAccept}`,
  docAndPdf: `${docAccept},${pdfAccept}`,
}

/** 上传文件大小限制(MB) */
const IMG_SIZE_LIMIT = 10

const pdfSuffix = '.pdf'
const videoSuffixList = ['.mp4', '.mov']
const imgSuffixList = ['.png', '.jpg', '.jpeg']

const checkIsVideoByName = name => {
  return videoSuffixList.some(videoSuffix => name.includes(videoSuffix))
}

const checkIsImgByName = name => {
  return imgSuffixList.some(videoSuffix => name.includes(videoSuffix))
}

const resolveFileList = list => {
  if (isEmpty(list)) {
    return []
  }
  if (typeof list === 'string') {
    list = list.split(',').map(item => ({
      name: item.substring(item.lastIndexOf('/') + 1),
      url: item,
      fileType: checkIsImgByName(item)
        ? FILE_TYPE_ENUM.IMAGE
        : item?.includes(pdfSuffix)
          ? PDF_IMG
          : FILE_TYPE_ENUM.IMAGE,
    }))
  }
  let fileList = []
  list.forEach(item => {
    if (typeof item === 'string') {
      item = {
        name: item.substring(item.lastIndexOf('/') + 1),
        url: item,
        fileType: checkIsImgByName(item)
          ? FILE_TYPE_ENUM.IMAGE
          : item?.includes(pdfSuffix)
            ? PDF_IMG
            : FILE_TYPE_ENUM.IMAGE,
      }
    }
    const { id, name, url, fileType, ...other } = item
    fileList.push({
      uid: id || uniqueId(),
      status: FILE_STATUS_ENUM.DONE,
      finish: true,
      name: name.substring(name.lastIndexOf('/') + 1),
      url,
      fileType: checkIsImgByName(name)
        ? FILE_TYPE_ENUM.IMAGE
        : name?.includes(pdfSuffix)
          ? PDF_IMG
          : FILE_TYPE_ENUM.IMAGE,
      thumbUrl: name?.includes(pdfSuffix) ? PDF_IMG : url,
      ...other,
    })
  })
  return fileList
}

/**
 * @typedef {object} AekUploadValue
 * @property {string | number} [uid] 文件唯一id
 * @property {string} [name] 文件名
 * @property {string} [url] 文件地址
 * @property {string} [thumbUrl] 缩略图地址
 * @property {FILE_TYPE_ENUM.value} [fileType] 文件类型
 * @property {'done' | 'error'} [status] 上传到后台的状态，上传到后台才有
 * @property {boolean} [finish] 是否上传结束，上传到后台才有
 * @property {File | Blob} [file] 文件对象，不上传到后台才有
 */

/**
 * @typedef {object} AekUploadProps
 * @property {AekUploadValue} [value]
 * @property {function(AekUploadValue[])} [onChange]
 * @property {number} [limitFileNum] 限制文件上传数量
 * @property {'unset' | 'doc' | 'execl' | 'img' | 'imgAndPdf' | 'imgAndVideo' | 'docAndPdf'} [fileType]
 * @property {'text' | 'picture' | 'picture-card'} [listType]
 * @property {boolean | import('antd/es/upload/interface').ShowUploadListInterface} [showUploadList] 是否显示上传列表
 * @property {boolean} [multiple] 是否支持多选
 * @property {object} [data] 上传所需额外参数
 * @property {boolean} [notUpLoad] 是否上传到服务器
 * @property {boolean} [stackingThumb] 是否堆叠图片
 */

/**
 *
 * @param {AekUploadProps} props
 * @returns
 */
const AekUpload = (props, ref) => {
  const {
    children,
    value,
    onChange,
    className,
    limitFileNum = 9,
    fileType = 'unset',
    listType = 'text',
    showUploadList = true,
    multiple = true,
    data = {},
    notUpLoad = false,
    stackingThumb = false,
  } = props
  const [fileList, setFileList] = useState([])

  useEffect(() => {
    // 经过测试暂时认为fileList长度发生变化才会更新
    notUpLoad && onChange && onChange(fileList)
  }, [fileList.length])

  useEffect(() => {
    if (JSON.stringify(value) === JSON.stringify(fileList)) return
    if (isEmpty(value)) {
      if (fileList.length > 0) {
        setFileList([])
      }
      return
    }
    let list = resolveFileList(value)
    setFileList(list)
    // 脱离当前生命周期，防止死循环
    setTimeout(() => {
      onChange && onChange(list)
    }, 0)
  }, [value])

  /** 上传前校验 */
  const beforeUpload = (_file, _fileList) => {
    const index = _fileList.findIndex(({ uid }) => uid === _file.uid)
    if (fileList.length + index + 1 > limitFileNum) {
      // 只需要提示一次就行了
      if (fileList.length + index + 1 === limitFileNum + 1) {
        message.warning(`最多上传${limitFileNum}个文件`)
      }
      return Upload.LIST_IGNORE
    }

    if (_file.size / 1024 / 1024 >= IMG_SIZE_LIMIT) {
      const text = `只能上传小于${IMG_SIZE_LIMIT}MB的文件`
      message.error(text)
      _file.finish = true
      _file.status = FILE_STATUS_ENUM.ERROR
      _file.thumbUrl = null
      _file.error = {
        statusText: text,
      }
      return Promise.reject()
    }

    if (fileType !== 'unset' && !acceptType[fileType].includes(_file.type)) {
      message.error('文件类型错误')
      return Upload.LIST_IGNORE
    }
    return !notUpLoad
  }

  const onFileChange = ({ fileList: _fileList }) => {
    console.debug('onChange-fileList', JSON.parse(JSON.stringify(_fileList)))

    const newFileList = [..._fileList]
    setFileList(newFileList)
    const everyFinish = _fileList.every(({ status }) => status !== undefined && status !== 'uploading')
    if (!everyFinish) return
    const changeFileList = []
    _fileList.forEach(file => {
      const { uid, response, finish, name } = file
      if (finish) {
        changeFileList.push(file)
        return
      }
      const { code, data } = response
      const url = file.serverUrl
      let tmpFile
      if (code === REQUEST_SUCCESS_CODE) {
        tmpFile = {
          uid,
          status: FILE_STATUS_ENUM.DONE,
          finish: true,
          name,
          // 文件地址
          url,
          // 缩略图地址
          thumbUrl: name?.includes(pdfSuffix) ? PDF_IMG : url,
          fileType: name.includes(pdfSuffix) ? FILE_TYPE_ENUM.PDF : FILE_TYPE_ENUM.IMAGE,
        }
      } else {
        tmpFile = {
          uid,
          status: FILE_STATUS_ENUM.ERROR,
          finish: true,
          name,
        }
      }
      changeFileList.push(tmpFile)
    })
    console.debug('onChange-newFileList', changeFileList)
    setFileList(changeFileList)
    onChange && onChange(changeFileList)
  }

  const onPreview = file => {
    Preview.PromiseThen({
      previewIndex: fileList
        .filter(({ status }) => status !== FILE_STATUS_ENUM.ERROR)
        .findIndex(({ uid }) => uid === file.uid),
      previewList: fileList
        .filter(({ status }) => status !== FILE_STATUS_ENUM.ERROR)
        .map(({ uid, fileType, thumbUrl, name, url }) => ({
          id: uid,
          type: fileType,
          thumbUrl,
          name,
          url,
        })),
    })
  }

  const getData = file => {
    let policyText = {
      expiration: '2099-01-01T12:00:00.000Z', // 设置该Policy的失效时间，超过这个失效时间之后，就没有办法通过这个policy上传文件了
      conditions: [
        ['content-length-range', 0, 1048576000], // 设置上传文件的大小限制
      ],
    }
    const uploadKey = 'LTAI5tSavpHpjbAfNtbfAbkx'
    const uploadSec = '6C4CVVbpeCSWu0Ygjox5QRrnhd0ZRv'
    let policyBase64 = Base64.encode(JSON.stringify(policyText))
    let bytes = Crypto.HMAC(Crypto.SHA1, policyBase64, uploadSec, { asBytes: true })
    const signature = Crypto.util.bytesToBase64(bytes)
    const key = `development/octopus/images/${dayjs().format('YYYY_MM_DD')}/${randomString()}${file.name}`
    const data = {
      key,
      policy: policyBase64,
      OSSAccessKeyId: uploadKey,
      // eslint-disable-next-line camelcase
      success_action_status: 200, // 让服务端返回200，不设置则默认返回204
      signature,
    }
    file.serverUrl = `/${key}`
    return data
  }

  return (
    <Upload
      ref={ref}
      className={`${styles.upload} ${stackingThumb && styles.stackingThumb} ${className}`}
      action={`/oss/upload`} // 上传的地址
      name='file' // 发到后台的文件参数名
      data={file => ({ ...data, ...getData(file) })} // 上传所需额外参数或返回上传额外参数的方法
      maxCount={limitFileNum}
      listType={listType}
      accept={acceptType[fileType]}
      multiple={multiple}
      showUploadList={showUploadList}
      fileList={fileList}
      beforeUpload={beforeUpload}
      onPreview={onPreview}
      onChange={onFileChange}
    >
      {limitFileNum > fileList.length && (children || (fileType.includes('img') ? imgUploadButton : uploadButton))}
    </Upload>
  )
}

export default forwardRef(AekUpload)
